﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Arcadium.Networking;

namespace Arcadium.GameEntities.Characters
{
    class NewCharacter : Character
    {
        #region Field

        struct CharacterState
        {
            public Vector2 Position;
        }

        CharacterState simulationState;

        CharacterState previousState;

        CharacterState displayState;

        // Used to interpolate displayState from previousState toward simulationState.
        float currentSmoothing;

        // Averaged time difference from the last 100 incoming packets, used to
        // estimate how our local clock compares to the time on the remote machine.
        RollingAverage clockDelta = new RollingAverage(100);

        #endregion

        #region Initialization

        public NewCharacter(World world) : base(world)
        {
            // TODO: Fix spawnpoints.
            simulationState.Position = world.Map.GetPlayerSpawnPoint(0);

            // Initialize all three versions of our state to the same values.
            previousState = simulationState;
            displayState = simulationState;

            // Load textures.
            SetTexture(CharacterType.Player);
        }

        #endregion

        /// <summary>
        /// Moves a locally controlled character in response to the specified inputs.
        /// </summary>
        public void UpdateLocal(InputState input, PlayerIndex playerIndex)
        {
            // Update the master simulation state.
            UpdateState(ref simulationState, input, playerIndex);

            // Locally controlled tanks have no prediction or smoothing, so we
            // just copy the simulation state directly into the display state.
            displayState = simulationState;
        }

        /// <summary>
        /// Applies prediction and smoothing to a remotely controlled character.
        /// </summary>
        public void UpdateRemote(int framesBetweenPackets)
        {
            // Update the smoothing amount, which interpolates from the previous
            // state toward the current simultation state. The speed of this decay
            // depends on the number of frames between packets: we want to finish
            // our smoothing interpolation at the same time the next packet is due.
            float smoothingDecay = 1.0f / framesBetweenPackets;

            currentSmoothing -= smoothingDecay;

            if (currentSmoothing < 0)
                currentSmoothing = 0;

            // Predict how the remote tank will move by updating
            // our local copy of its simultation state.
            UpdateState(ref simulationState, null, PlayerIndex.One);

            // If both smoothing and prediction are active,
            // also apply prediction to the previous state.
            if (currentSmoothing > 0)
            {
                UpdateState(ref previousState, null, PlayerIndex.One);
            }

            if (currentSmoothing > 0)
            {
                // Interpolate the display state gradually from the
                // previous state to the current simultation state.
                ApplySmoothing();
            }
            else
            {
                // Copy the simulation state directly into the display state.
                displayState = simulationState;
            }
        }


        /// <summary>
        /// Applies smoothing by interpolating the display state somewhere
        /// in between the previous state and current simulation state.
        /// </summary>
        void ApplySmoothing()
        {
            displayState.Position = Vector2.Lerp(simulationState.Position,
                                                 previousState.Position,
                                                 currentSmoothing);

            //displayState.Velocity = Vector2.Lerp(simulationState.Velocity,
            //                                     previousState.Velocity,
            //                                     currentSmoothing);

            //displayState.TankRotation = MathHelper.Lerp(simulationState.TankRotation,
            //                                            previousState.TankRotation,
            //                                            currentSmoothing);

            //displayState.TurretRotation = MathHelper.Lerp(simulationState.TurretRotation,
            //                                              previousState.TurretRotation,
            //                                              currentSmoothing);
        }




        public void WriteNetworkPacket(PacketWriter packetWriter, GameTime gameTime)
        {
            // Send our current time.
            packetWriter.Write((float)gameTime.TotalGameTime.TotalSeconds);

            // Send the current state of the tank.
            packetWriter.Write(Position);
        }

        public void ReadNetworkPacket(PacketReader packetReader, GameTime gameTime, TimeSpan latency)
        {
            // Start a new smoothing interpolation from our current
            // state toward this new state we just received.
            previousState = displayState;
            currentSmoothing = 1;

            // Read what time this packet was sent.
            float packetSendTime = packetReader.ReadSingle();

            // Read simulation state from the network packet.
            Position = packetReader.ReadVector2();

            // Apply prediction to compensate for
            // how long it took this packet to reach us.
            //ApplyPrediction(gameTime, latency, packetSendTime);
        }



        /// <summary>
        /// Incoming network packets tell us where the tank was at the time the packet
        /// was sent. But packets do not arrive instantly! We want to know where the
        /// tank is now, not just where it used to be. This method attempts to guess
        /// the current state by figuring out how long the packet took to arrive, then
        /// running the appropriate number of local updates to catch up to that time.
        /// This allows us to figure out things like "it used to be over there, and it
        /// was moving that way while turning to the left, so assuming it carried on
        /// using those same inputs, it should now be over here".
        /// </summary>
        void ApplyPrediction(GameTime gameTime, TimeSpan latency, float packetSendTime)
        {
            // Work out the difference between our current local time
            // and the remote time at which this packet was sent.
            float localTime = (float)gameTime.TotalGameTime.TotalSeconds;

            float timeDelta = localTime - packetSendTime;

            // Maintain a rolling average of time deltas from the last 100 packets.
            clockDelta.AddValue(timeDelta);

            // The caller passed in an estimate of the average network latency, which
            // is provided by the XNA Framework networking layer. But not all packets
            // will take exactly that average amount of time to arrive! To handle
            // varying latencies per packet, we include the send time as part of our
            // packet data. By comparing this with a rolling average of the last 100
            // send times, we can detect packets that are later or earlier than usual,
            // even without having synchronized clocks between the two machines. We
            // then adjust our average latency estimate by this per-packet deviation.

            float timeDeviation = timeDelta - clockDelta.AverageValue;

            latency += TimeSpan.FromSeconds(timeDeviation);

            TimeSpan oneFrame = TimeSpan.FromSeconds(1.0 / 60.0);

            // Apply prediction by updating our simulation state however
            // many times is necessary to catch up to the current time.
            while (latency >= oneFrame)
            {
                UpdateState(ref simulationState, null, PlayerIndex.One);

                latency -= oneFrame;
            }
        }




        /// <summary>
        /// Updates one of our state structures, using the current inputs to turn
        /// the tank, and applying the velocity and inertia calculations. This
        /// method is used directly to update locally controlled tanks, and also
        /// indirectly to predict the motion of remote tanks.
        /// </summary>
        void UpdateState(ref CharacterState state, InputState input, PlayerIndex playerIndex)
        {
            if (input.IsKeyDown(Keys.Left, playerIndex))
                state.Position.X -= 10;
            if (input.IsKeyDown(Keys.Right, playerIndex))
                state.Position.X += 10;
        }










    }
}
